% requires types.ps to be included first

% ast print_readably -> _pr_str -> string
/_pr_str { 4 dict begin
    /print_readably exch def
    dup xcheck { (Cannot print proc: ) print dup == quit } if % assert
    /obj exch def
    obj _sequential? {
        obj _list? { (\() (\)) }{ ([) (]) } ifelse
        obj /data get ( ) print_readably _pr_str_args
        exch concatenate concatenate
    }{ obj _hash_map? {
        ({)
        % get array of contents with keys stringified
        [ obj /data get { exch dup length string cvs exch } forall ]
        ( ) print_readably _pr_str_args
        concatenate
        (}) concatenate
    }{ obj _function? { % if builtin function
        (<\(builtin_fn* {)
        obj /data get dup length array copy cvlit
        ( ) print_readably _pr_str_args
        (}>)
        concatenate concatenate
    }{ obj _mal_function? { % if user defined mal_function
        (<\(fn* )
        obj /params get print_readably _pr_str
        ( )
        obj /ast get print_readably _pr_str
        (\)>)
        concatenate concatenate concatenate concatenate
    }{ obj _atom? { % if atom
        (\(atom ) 
        obj /data get print_readably _pr_str
        (\))
        concatenate concatenate
    }{ /arraytype obj type eq { % if list or code block
        % accumulate an array of strings
        (\()
        obj ( ) print_readably _pr_str_args
        concatenate
        (\))
        concatenate
    }{ /integertype obj type eq { % if number
        /slen
          obj abs 1 max log floor cvi 1 add % positive size
          obj 0 lt { 1 add } if % account for sign
        def
        obj 10 slen string cvrs
    }{ /stringtype obj type eq { % if string
        obj length 0 gt { % if string length > 0
            obj 0 get 127 eq { %if starts with 0x7f (keyword)
                obj dup length string copy
                dup 0 58 put % 58 is ':'
            }{ print_readably {
                (")
                obj (\\) (\\\\) replace
                    (") (\\") replace
                    (\n) (\\n) replace
                (") concatenate concatenate
            }{
                obj
            } ifelse } ifelse
        }{ % else empty string
            print_readably {
                ("")
            }{
                obj
            } ifelse
        } ifelse
    }{ null obj eq { % if nil
        (nil)
    }{ true obj eq { % if true
        (true)
    }{ false obj eq { % if false
        (false)
    }{ /nametype obj type eq { % if symbol
        obj dup length string cvs
    }{
        (<unknown>)  
    } ifelse } ifelse } ifelse } ifelse } ifelse } ifelse } ifelse } ifelse } ifelse } ifelse } ifelse } ifelse
end } def

% array delim print_readably -> _pr_str_args -> new_string
/_pr_str_args { 3 dict begin
    /print_readably exch def
    /delim exch def
    /args exch def
    ()
    args length 0 gt { %if any elements
        [
            args { %foreach argument in array
                dup xcheck { %if executable
                    255 string cvs
                }{
                    print_readably _pr_str
                } ifelse
            } forall 
        ]
        { concatenate delim concatenate } forall
        dup length delim length sub 0 exch getinterval % strip off final delim
    } if
end } def

% utility function
/print_dict {
    (DICT contents:\n) print
    {
        (  - ) print
        exch dup length string cvs print % key
        (: ) print
        ==
    } forall
} def

